package gogen

import (
	_ "embed"
	"fmt"
	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
	"os"
	"path/filepath"
	"strings"

	"github.com/zeromicro/go-zero/tools/goctl/api/spec"
)

//go:embed logic.tpl
var logicTemplate string

//go:embed isexits.tpl
var isexitsTemplate string

func genLogics(dir, rootPkg string, api *spec.ApiSpec) error {

	docs := make(map[string]map[string]string)

	for _, apis := range api.Srvs {
		for _, group := range apis.Service.Groups {
			for _, route := range group.Routes {
				logicDoc := route.GetAnnotation("logicDoc")

				if len(logicDoc) == 0 {
					continue
				}

				logic := route.GetAnnotation("logic")
				if len(logic) == 0 {
					continue
				}
				logicStrs := strings.Split(logic, ".")

				if _, ok := docs[logicStrs[0]]; !ok {
					docs[logicStrs[0]] = make(map[string]string)
				}

				docs[logicStrs[0]][logicStrs[1]] = logicDoc
			}
		}
	}

	for _, logic := range api.Logics {
		genLogic(dir, rootPkg, api.Info, logic, docs)
	}
	return nil
}

// ---------------------------------------------------------------------

func genLogic(dir, rootPkg string, info spec.Info, logic *spec.LogicSpec, docs map[string]map[string]string) error {

	// 验证文件是否存在
	filename := filepath.Join(dir, fmt.Sprintf("%slogic/%s.go", internal, strings.Title(logic.Name)))
	exists, err := PathExists(filename)
	if err != nil {
		return err
	}

	if !exists {
		return genFile(genLogicNoExists(dir, rootPkg, logic, docs))
	}

	mod, ok := info.Properties["serviceMod"]
	if !ok || mod != Processing {
		// 不处理
		return nil
	}

	cfg, err := genLogicExists(dir, filename, rootPkg, logic, docs)
	if err != nil {
		return err
	}
	if len(cfg.dir) == 0 {
		return nil
	}

	return genFile(cfg)
}

func genLogicExists(dir, filename, rootPkg string, logic *spec.LogicSpec, docs map[string]map[string]string) (fileGenConfig, error) {

	var isExistsNew bool
	methods := make(map[string]struct{})
	content, err := addContentToAFile(filename, fmt.Sprintf("type %s interface", logic.Name), func(s string) {
		m := strings.TrimPrefix(s, "\t")
		if len(m) == 0 || strings.Contains(s, "//") {
			return
		}
		idx := strings.Index(m, "(")
		if idx == -1 {
			return
		}
		methods[m[:idx]] = struct{}{}
	}, func(positionStartIdx, positionEndIdx int, contents []string) []string {
		addMethods, addInterfaceMethods, _ := genLogicMethods(logic, methods, docs)
		if len(addMethods) == 0 || len(addInterfaceMethods) == 0 {
			return contents
		}

		// 在接口处添加内容
		contents = append(contents[:positionEndIdx], append([]string{addInterfaceMethods}, contents[positionEndIdx:]...)...)
		// 在结尾处增加内容
		contents = append(contents, addMethods)
		isExistsNew = true
		return contents
	})
	if err != nil || !isExistsNew {
		return fileGenConfig{}, err
	}

	os.Remove(filename)

	return fileGenConfig{
		dir:             dir,
		subdir:          fmt.Sprintf("%slogic", internal),
		filename:        fmt.Sprintf("%s.go", strings.ToLower(logic.Name)),
		templateName:    "isexitsTemplate",
		category:        category,
		templateFile:    isexitsTemplateFile,
		builtinTemplate: isexitsTemplate,
		data: map[string]any{
			"content": content,
		},
	}, nil
}

func genLogicNoExists(dir, rootPkg string, logic *spec.LogicSpec, docs map[string]map[string]string) fileGenConfig {

	methods, interfaceMethods, isDomain := genLogicMethods(logic, nil, docs)

	return fileGenConfig{
		dir:             dir,
		subdir:          fmt.Sprintf("%slogic", internal),
		filename:        fmt.Sprintf("%s.go", strings.ToLower(logic.Name)),
		templateName:    "logicTemplate",
		category:        category,
		templateFile:    logicTemplateFile,
		builtinTemplate: logicTemplate,
		data: map[string]any{
			"interfaceMethods": interfaceMethods,
			"interface":        logic.Name,
			"methods":          methods,
			"newLogic":         fmt.Sprintf("New%s", logic.Name),
			"pkgName":          "logic",
			"imports":          genLogicImports(isDomain, methods, rootPkg),
			"logic":            logic.NameLower,
			"function":         strings.Title(strings.TrimSuffix("", "Logic")),
		},
	}
}

func genLogicMethods(logic *spec.LogicSpec, filter map[string]struct{}, docs map[string]map[string]string) (methodList, interfaceMethods string, isDomain bool) {
	var (
		resMethod           strings.Builder
		resInterfaceMethods strings.Builder
	)

	for k, method := range logic.Methods {
		if filter != nil && len(filter) > 0 {
			if _, ok := filter[method.Name]; ok {
				continue
			}
		}

		if docs != nil && docs[logic.Name] != nil && len(docs[logic.Name][method.Name]) > 0 {
			resMethod.Write([]byte(fmt.Sprintf("// %s\n\tfunc (l *%s) %s (ctx context.Context", docs[logic.Name][method.Name], logic.NameLower, method.Name)))
			resInterfaceMethods.Write([]byte(fmt.Sprintf("// %s\n%s(ctx context.Context", docs[logic.Name][method.Name], method.Name)))
		} else {
			resMethod.Write([]byte(fmt.Sprintf("func (l *%s) %s (ctx context.Context", logic.NameLower, method.Name)))
			resInterfaceMethods.Write([]byte(fmt.Sprintf("%s(ctx context.Context", method.Name)))
		}

		// req
		if method.RequestType != nil {
			isDomain = true
			req := fmt.Sprintf(", req *%s", requestGoTypeName(method, typesPacket))
			resMethod.Write([]byte(req))
			resInterfaceMethods.Write([]byte(req))
		}
		resMethod.Write([]byte(") ("))
		resInterfaceMethods.Write([]byte(") ("))
		// resp and body
		if method.ResponseType != nil {
			isDomain = true
			resp := fmt.Sprintf("resp %s, ", responseGoTypeName(method, typesPacket))
			resMethod.Write([]byte(resp))
			resInterfaceMethods.Write([]byte(resp))
		}
		resInterfaceMethods.Write([]byte("err error)"))
		if len(logic.Methods) > k+1 {
			resInterfaceMethods.Write([]byte("\n\t"))
		}
		resMethod.Write([]byte("err error){\n\treturn\n}\n\n"))
	}

	return resMethod.String(), resInterfaceMethods.String(), isDomain
}

func genLogicImports(isDomain bool, methods, parentPkg string) string {
	var imports []string
	if len(methods) > 0 {
		imports = append(imports, `"context"`+"\n")
	}
	imports = append(imports, fmt.Sprintf("\"%s\"", pathx.JoinPackages(parentPkg, internal+"svc")))
	if isDomain {
		imports = append(imports, fmt.Sprintf("\"%s\"", pathx.JoinPackages(parentPkg, internal+"domain")))
	}
	return strings.Join(imports, "\n\t")
}
